/*	--- HTTP编程 --- 

Q: 什么是HTTP？

A: HTTP就是目前使用最广泛的Web应用程序使用的基础协议. 
例如: 浏览器访问网站，手机App访问后台服务器，都是通过HTTP协议实现的。

---------

HTTP是【HyperText Transfer Protocol】的缩写，翻译为【超文本传输协议】，它是基于【TCP协议之上】的一种【请求-响应协议】。


我们来看一下浏览器请求访问某个网站时发送的【HTTP请求-响应】。

当浏览器希望访问某个网站时，浏览器和网站服务器之间首先建立【TCP连接】，且服务器总是使用【80端口】和【加密端口443】;

然后，【浏览器】向【服务器】发送【一个HTTP请求】，【服务器】收到后，返回【一个HTTP响应】，并且在【响应】中包含了【HTML的网页内容】

这样，【浏览器】解析【HTML】后就可以给用户显示网页了。

一个完整的【HTTP请求-响应】如下：


            GET / HTTP/1.1
            Host: www.sina.com.cn
            User-Agent: Mozilla/5 MSIE (MSIE=浏览器接口)
            Accept: * /*               ┌────────┐
┌─────────┐ Accept-Language: zh-CN,en  │░░░░░░░░│
│O ░░░░░░░│───────────────────────────>├────────┤
├─────────┤<───────────────────────────│░░░░░░░░│
│         │ HTTP/1.1 200 OK            ├────────┤
│         │ Content-Type: text/html    │░░░░░░░░│
└─────────┘ Content-Length: 133251     └────────┘
  Browser   <!DOCTYPE html>              Server
            <html><body>
            <h1>Hello</h1>
            ...



【HTTP请求】的格式是固定的。

它由【HTTP Header】和【HTTP Body】两部分构成。

第一行总是【请求方法 路径 HTTP版本】，例如，【GET / HTTP/1.1】表示使用【GET请求】，【路径】是【/】，【版本】是【HTTP/1.1】。

后续的每一行都是【固定的Header: Value格式】，我们称为【HTTP Header】，服务器依靠某些【特定的Header】来识别【客户端请求】，例如：

	· Host：表示请求的域名，因为【一台服务器】上可能有【多个网站】，因此有必要依靠【Host】来识别于【请求】;
	
	· User-Agent：表示【客户端】自身【标识信息】，不同的浏览器有不同的标识，服务器依靠【User-Agent】判断【客户端类型】;
	
	· Accept：表示【客户端】能处理的【HTTP响应格式】，【* /*】表示 任意格式，【text/*】表示 任意文本，【image/png】表示 PNG格式的图片;
	
	· Accept-Language：表示【客户端】接收的【语】言，【多种语言】按【优先级】排序，【服务器】依靠该【Accept-Language】给用户返回【特定语言编码的网页版本】。


-------------------

#	HTTP请求 —— GET/POST


如果是【GET请求】，那么该【HTTP请求】只有【HTTP Header】，没有[HTTP Body]。

如果是【POST请求】，那么该【HTTP请求】带有【Body】，以【一个空行】分隔。

一个典型的【带Body的HTTP请求】如下(POST请求)：

	POST /login HTTP/1.1
	Host: www.example.com
	Content-Type: application/x-www-form-urlencoded
	Content-Length: 30

	username=hello&password=123456


【POST请求】通常要设置【Content-Type】表示【Body的类型】，【Content-Length】表示【Body的长度】，这样【服务器】就可以根据【请求的Header和Body】做出正确的响应。


此外，【GET请求的参数】必须附加在【URL】上，并以【URLEncode方式】编码，例如：http://www.example.com/?a=1&b=K%26R，参数分别是a=1和b=K&R。

因为URL的长度限制，【GET请求的参数】不能太多，而【POST请求的参数】就没有长度限制，因为【POST请求的参数】必须放到【Body】中。

并且，【POST请求的参数】不一定是【URL编码】，可以按【任意格式编码】，只需要在【Content-Type】中正确设置即可。


常见的发送【JSON】的【POST请求】如下：

	POST /login HTTP/1.1
	Content-Type: application/json
	Content-Length: 38

	{"username":"bob","password":"123456"}  <--- Body部分



【HTTPResponse, Http响应】也是由【Header】和【Body】两部分组成，一个典型的【HTTP响应】如下：

	HTTP/1.1 200 OK
	Content-Type: text/html
	Content-Length: 133251

	<!DOCTYPE html>
	<html><body>
	<h1>Hello</h1>
	...



响应的第一行总是【HTTP版本 响应代码 响应说明】，例如，【HTTP/1.1 200 OK】表示【版本是HTTP/1.1】，【响应代码是200】，【响应说明是OK】。

【客户端】只依赖【响应代码】判断【HTTP响应】是否成功。

【HTTP】有【固定的响应代码】：

	· 1xx：表示一个【提示性响应】，例如【101】表示将【切换协议】，常见于WebSocket连接;

	· 2xx：表示一个【成功的响应】，例如【200】表示【成功】，【206】表示【只发送了部分内容】;

	· 3xx：表示一个【重定向的响应】，例如【301】表示【永久重定向】，【303】表示【客户端应该按指定路径重新发送请求】;

	· 4xx：表示一个【因为客户端问题导致的错误响应】，例如【400】表示【因为Content-Type等各种原因导致的无效请求】，【404】表示【指定的Host路径不存在】;

	· 5xx：表示一个【因为服务器问题导致的错误响应】，例如【500】表示【服务器内部故障】，【503】表示【服务器暂时无法响应】。



当【浏览器Browser/Client】收到【第一个HTTP响应】后，它解析HTML后，又会发送【一系列HTTP请求】，例如，【GET /logo.jpg HTTP/1.1】请求一个图片，【服务器Server】响应【图片请求】后，会直接把【二进制内容的图片】发送给【浏览器Client】：

	HTTP/1.1 200 OK
	Content-Type: image/jpeg
	Content-Length: 18391

	????JFIFHH??XExifMM?i&??X?...(二进制的JPEG图片)



-----------


因此，【服务器】总是被动地接收【客户端】的【一个HTTP请求】，然后【响应】它。

【客户端】则根据需要发送【若干个HTTP请求】。

对于【最早期的HTTP/1.0】协议，每次发送一个HTTP请求，客户端都需要先创建一个新的TCP连接，然后，收到服务器响应后，关闭这个TCP连接。

由于【建立TCP连接】就比较耗时，因此，为了提高效率，HTTP/1.1协议允许在【一个TCP连接】中反复【发送-响应】，这样就能大大提高效率：

(也就是, 一次TCP连接后, 不再新建TCP连接, 长期使用该TCP连接)


	                       ┌─────────┐
	┌─────────┐            │░░░░░░░░░│
	│O ░░░░░░░│            ├─────────┤
	├─────────┤            │░░░░░░░░░│
	│         │            ├─────────┤
	│         │            │░░░░░░░░░│
	└─────────┘            └─────────┘
	     │      request 1       │
	     │─────────────────────>│
	     │      response 1      │
	     │<─────────────────────│
	     │      request 2       │
	     │─────────────────────>│
	     │      response 2      │
	     │<─────────────────────│
	     │      request 3       │
	     │─────────────────────>│
	     │      response 3      │
	     │<─────────────────────│
	     ▼                      ▼



因为【HTTP协议】是一个【请求-响应协议】，【客户端】在发送了一个【HTTP请求】后，必须等待【服务器】响应后，才能发送下一个请求，这样一来，如果某个响应太慢，它就会堵住后面的请求。

所以，为了进一步提速，【HTTP/2.0】允许【客户端】在没有收到【响应】的时候，发送【多个HTTP请求】，【服务器】返回【响应】的时候，不一定按顺序返回，只要双方能识别出【哪个响应Response】对应【哪个请求Request】，就可以做到并行发送和接收：

	                       ┌─────────┐
	┌─────────┐            │░░░░░░░░░│
	│O ░░░░░░░│            ├─────────┤
	├─────────┤            │░░░░░░░░░│
	│         │            ├─────────┤
	│         │            │░░░░░░░░░│
	└─────────┘            └─────────┘
	     │      request 1       │
	     │─────────────────────>│
	     │      request 2       │
	     │─────────────────────>│
	     │      response 1      │
	     │<─────────────────────│
	     │      request 3       │
	     │─────────────────────>│
	     │      response 3      │
	     │<─────────────────────│
	     │      response 2      │
	     │<─────────────────────│
	     ▼                      ▼
	

可见，【HTTP/2.0】进一步提高了效率。


===============================================



#	HTTP编程


既然【HTTP】涉及到【客户端】和【服务器端】，和TCP类似，我们也需要针对客户端编程和针对服务器端编程。


本节我们不讨论服务器端的HTTP编程，因为【服务器端的HTTP编程】本质上就是【编写Web服务器】，这是一个【非常复杂的体系】，也是【JavaEE开发】的核心内容，我们在后面的章节再仔细研究。


本节我们只讨论作为【客户端的HTTP编程】。


因为【浏览器】也是一种【HTTP客户端】，所以，【客户端的HTTP编程】，它的行为本质上和【浏览器】是一样的，即发送一个HTTP请求，接收服务器响应后，获得响应内容。

只不过【浏览器】进一步把【响应内容】解析后渲染并展示给了用户，而我们使用Java进行【HTTP客户端编程】仅限于获得【响应内容】。


我们来看一下Java如果使用【HTTP客户端编程】。


Java标准库提供了基于HTTP的包
但是要注意，早期的JDK版本是通过【HttpURLConnection】访问HTTP，典型代码如下： */

URL url = new URL("http://www.example.com/path/to/target?a=1&b=2");
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // URL.openConnection();

// 设置HttpURLConnection
conn.setRequestMethod("GET"); // set请求方法
conn.setUseCaches(false); // set使用缓存们
conn.setConnectTimeout(5000); // set连接请求超时5秒=5000ms

// 设置HTTP头:
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 11; Windows NT 5.1)");

// 连接并发送HTTP请求:
conn.connect();

// 判断HTTP响应是否200:
if (conn.getResponseCode() != 200) {
    throw new RuntimeException("bad response");
}		

// 获取所有响应Header:
Map<String, List<String>> map = conn.getHeaderFields();
for (String key : map.keySet()) {
    System.out.println(key + ": " + map.get(key));
}

// 获取响应内容:
InputStream input = conn.getInputStream();
...



/*
上述代码编写比较繁琐，并且需要【手动】处理【InputStream】，所以用起来很麻烦。


=================================


从Java 11开始，引入了【新的HttpClient】，它使用【链式调用的API】，能大大简化【HTTP的处理】。

我们来看一下如何使用【新版的HttpClient】。

首先需要创建一个【全局HttpClient实例】，因为【HttpClient】内部使用【线程池优化多个HTTP连接】，可以复用：*/

static HttpClient httpClient = HttpClient.newBuilder().build(); // static 全局


// 使用【GET请求】获取【文本内容】 代码如下：
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpClient.Version;
import java.time.Duration;
import java.util.*;

public class Main {
    // 全局HttpClient:
    static HttpClient httpClient = HttpClient.newBuilder().build();

    public static void main(String[] args) throws Exception {
        String url = "https://www.sina.com.cn/";
        HttpRequest request = HttpRequest.newBuilder(new URI(url))

            // 设置Header:
            .header("User-Agent", "Java HttpClient")
            .header("Accept", "*/*")

            // 设置超时:
            .timeout(Duration.ofSeconds(5))

            // 设置版本:
            .version(Version.HTTP_2)
            .build();

        // HttpClient.send(), return HttpResponse<String>
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        //输出Header
        // HTTP允许重复的Header，因此一个Header可对应多个Value:
        Map<String, List<String>> headers = response.headers().map();

        for (String header : headers.keySet()) {
            System.out.println(header + ": " + headers.get(header).get(0));
        }

        //输出Body内容
        System.out.println(response.body().substring(0, 1024) + "...");
    }
}



/*
如果我们要获取图片这样的二进制内容，只需要把HttpResponse.BodyHandlers.ofString()换成HttpResponse.BodyHandlers.ofByteArray()，就可以获得一个HttpResponse<byte[]>对象。如果响应的内容很大，不希望一次性全部加载到内存，可以使用HttpResponse.BodyHandlers.ofInputStream()获取一个InputStream流。

要使用【POST请求】，我们要准备好发送的Body数据并正确设置Content-Type：*/
String url = "http://www.example.com/login";
String body = "username=bob&password=123456";

HttpRequest request = HttpRequest.newBuilder(new URI(url))
    // 设置Header:
    .header("Accept", "*/*")
    .header("Content-Type", "application/x-www-form-urlencoded")

    // 设置超时:
    .timeout(Duration.ofSeconds(5))

    // 设置版本:
    .version(Version.HTTP_2)

    // 使用【POST】并设置Body:
    .POST(BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
String s = response.body();


// 可见发送【POST数据】也十分简单。




/*=========================================


#	----- HTTP编程 ----- 


1. Java提供了【HttpClient 接口】 作为【新的HTTP客户端编程接口】用于取代【老的HttpURLConnection接口】;

2. 【HttpClient】使用【链式调用】并通过【内置的BodyPublishers】和【BodyHandlers】来更方便地处理数据。

3. 【浏览器】和【网站服务器】之间首先建立【TCP连接】，且服务器总是使用【80端口】和【加密端口443】

4. 服务器依靠某些特定的HTTP Header来识别客户端请求，例如：

	· Host：表示请求的域名，因为一台服务器上可能有多个网站，因此有必要依靠Host来识别用于请求;
	
	· User-Agent：表示客户端自身标识信息，不同的浏览器有不同的标识，服务器依靠User-Agent判断客户端类型;
	
	· Accept：表示客户端能处理的HTTP响应格式，* /*表示任意格式，text/*表示任意文本，image/png表示PNG格式的图片;
	
	· Accept-Language：表示客户端接收的语言，多种语言按优先级排序，服务器依靠该字段给用户返回特定语言的网页版本。


5. 【HTTP】有【固定的响应代码】：

	· 1xx：表示一个【提示性响应】，例如【101】表示将【切换协议】，常见于WebSocket连接;

	· 2xx：表示一个【成功的响应】，例如【200】表示【成功】，【206】表示【只发送了部分内容】;

	· 3xx：表示一个【重定向的响应】，例如【301】表示【永久重定向】，【303】表示【客户端应该按指定路径重新发送请求】;

	· 4xx：表示一个【因为客户端问题导致的错误响应】，例如【400】表示【因为Content-Type等各种原因导致的无效请求】，【404】表示【指定的Host路径不存在】;

	· 5xx：表示一个【因为服务器问题导致的错误响应】，例如【500】表示【服务器内部故障】，【503】表示【服务器暂时无法响应】。


6. 【GET请求】，那么该HTTP请求只有【HTTP Header】，没有【HTTP Body】。
如果是【POST请求】，那么该【HTTP请求】带有【Body】，以【一个空行】分隔。

	一个典型的POST方法中,带Body的HTTP请求如下：

		POST /login HTTP/1.1
		Host: www.example.com
		Content-Type: application/x-www-form-urlencoded
		Content-Length: 30

		username=hello&password=123456  <--- HTTP Body

----------------------------------------------------


一个完整的HTTP请求-响应如下：
(第一行为【请求方法 路径 HTTP版本号】, 后续的每一行都是固定的Header: Value格式)

            GET / HTTP/1.1
            Host: www.sina.com.cn
            User-Agent: Mozilla/5 MSIE
            Accept: * /*               ┌────────┐
┌─────────┐ Accept-Language: zh-CN,en  │░░░░░░░░│
│O ░░░░░░░│───────────────────────────>├────────┤
├─────────┤<───────────────────────────│░░░░░░░░│
│         │ HTTP/1.1 200 OK            ├────────┤
│         │ Content-Type: text/html    │░░░░░░░░│
└─────────┘ Content-Length: 133251     └────────┘
  Browser   <!DOCTYPE html>              Server
            <html><body>
            <h1>Hello</h1>
            ...


--------------------------------------------------


*/






































